"""AI Tag Extraction Engine with LRU Cache (替代Redis)."""

from __future__ import annotations

import hashlib
import json
import re
from functools import lru_cache
from typing import Any, List, Optional

from pydantic import BaseModel, Field


class TagResult(BaseModel):
    """AI标签提取结果."""

    name: str = Field(..., description="标签名称")
    confidence: float = Field(..., ge=0.0, le=1.0, description="置信度0-1")


class ExtractionResult(BaseModel):
    """完整提取结果."""

    tags: List[TagResult] = Field(default_factory=list, description="提取的标签列表")
    summary: Optional[str] = Field(None, max_length=100, description="核心需求摘要")
    method: str = Field(..., description="提取方法: rule/ai/hybrid")


class RuleEngine:
    """规则引擎 - 覆盖80%场景，零成本."""

    # 配套需求规则
    FACILITY_PATTERNS = {
        "可养宠物": [r"养宠", r"宠物", r"猫狗"],
        "需要车位": [r"车位", r"停车", r"车库"],
        "近地铁": [r"地铁", r"轨道", r"站点"],
        "学区房": [r"学区", r"学校", r"教育"],
        "商圈": [r"商圈", r"商场", r"购物"],
        "医院": [r"医院", r"医疗", r"诊所"],
    }

    # 环境要求规则
    ENVIRONMENT_PATTERNS = {
        "安静": [r"安静", r"清静", r"不吵"],
        "采光好": [r"采光", r"光线", r"阳光", r"明亮"],
        "朝向南": [r"朝南", r"南向", r"正南"],
        "高楼层": [r"高层", r"顶层", r"\d{2,}层"],
        "低楼层": [r"低层", r"底层", r"[1-5]层"],
        "景观好": [r"景观", r"视野", r"风景"],
    }

    # 特殊需求规则
    SPECIAL_PATTERNS = {
        "短租": [r"短租", r"月租", r"临时"],
        "拎包入住": [r"拎包", r"精装", r"全配"],
        "可注册公司": [r"注册", r"公司", r"营业"],
        "可改造": [r"改造", r"装修", r"翻新"],
        "押一付一": [r"押一", r"月付"],
    }

    def extract(self, text: str) -> List[TagResult]:
        """基于规则提取标签."""
        if not text:
            return []

        text = text.lower()
        results = []

        # 扫描所有规则组
        for tag_group in [
            self.FACILITY_PATTERNS,
            self.ENVIRONMENT_PATTERNS,
            self.SPECIAL_PATTERNS,
        ]:
            for tag_name, patterns in tag_group.items():
                for pattern in patterns:
                    if re.search(pattern, text, re.IGNORECASE):
                        results.append(
                            TagResult(
                                name=tag_name,
                                confidence=0.90,  # 规则引擎高置信度
                            )
                        )
                        break  # 匹配到一个即可

        # 去重
        seen = set()
        unique_results = []
        for tag in results:
            if tag.name not in seen:
                seen.add(tag.name)
                unique_results.append(tag)

        return unique_results


class HybridTagEngine:
    """混合标签引擎: 规则引擎(80%) + AI API(20%) + LRU缓存 + 多AI回退."""

    def __init__(
        self,
        qwen_api_key: Optional[str] = None,
        hunyuan_secret_id: Optional[str] = None,
        hunyuan_secret_key: Optional[str] = None,
        cache_size: int = 2000,
    ):
        """
        初始化混合引擎.

        Args:
            qwen_api_key: Qwen3 API密钥（主）
            hunyuan_secret_id: 腾讯混元SecretId（备）
            hunyuan_secret_key: 腾讯混元SecretKey（备）
            cache_size: LRU缓存大小，默认2000条
        """
        self.rule_engine = RuleEngine()
        self.qwen_api_key = qwen_api_key
        self.hunyuan_secret_id = hunyuan_secret_id
        self.hunyuan_secret_key = hunyuan_secret_key
        self._cache_size = cache_size

    @lru_cache(maxsize=2000)
    def _extract_cached(self, text_hash: str, text: str) -> str:
        """
        缓存提取结果 (JSON字符串).

        使用LRU缓存替代Redis:
        - maxsize=2000: 缓存最近2000次调用结果
        - 自动淘汰最久未使用的缓存
        - 零配置，零运维成本

        Args:
            text_hash: 文本MD5哈希（用于缓存key，lru_cache需要它作为不同的参数）
            text: 原始文本

        Returns:
            ExtractionResult的JSON字符串
        """
        # text_hash 用于 lru_cache 区分不同的缓存条目
        _ = text_hash  # 标记为有意使用
        # 阶段1: 规则引擎扫描
        rule_tags = self.rule_engine.extract(text)

        # 阶段2: 决策是否调用AI
        if self._should_use_ai(text, rule_tags):
            # 调用AI（支持回退：Qwen3 → 腾讯混元）
            ai_tags = self._call_ai_with_fallback(text)
            merged_tags = self._merge_tags(rule_tags, ai_tags)
            result = ExtractionResult(
                tags=merged_tags, summary=self._generate_summary(merged_tags, text), method="hybrid"
            )
        else:
            result = ExtractionResult(
                tags=rule_tags, summary=self._generate_summary(rule_tags, text), method="rule"
            )

        return result.model_dump_json()

    def extract(self, text: str) -> ExtractionResult:
        """
        提取标签（带LRU缓存）.

        Args:
            text: 客户备注或描述文本

        Returns:
            提取结果
        """
        if not text or len(text.strip()) < 2:
            return ExtractionResult(tags=[], summary=None, method="rule")

        # 生成文本哈希用于缓存
        text_hash = hashlib.md5(text.encode("utf-8")).hexdigest()

        # 调用缓存方法
        result_json = self._extract_cached(text_hash, text)
        return ExtractionResult.model_validate_json(result_json)

    def _should_use_ai(self, text: str, rule_tags: List[TagResult]) -> bool:
        """
        决策是否需要调用AI.

        策略:
        - 规则引擎已提取>=3个标签 → 不调用AI (80%场景)
        - 文本长度<20字 → 不调用AI
        - 无任何API密钥 → 不调用AI
        - 其他 → 调用AI (20%场景)
        """
        # 检查是否有任何可用AI
        has_ai = self.qwen_api_key or (self.hunyuan_secret_id and self.hunyuan_secret_key)
        if not has_ai:
            return False

        if len(rule_tags) >= 3:
            return False

        if len(text) < 20:
            return False

        return True

    def _call_ai_with_fallback(self, text: str) -> List[TagResult]:
        """
        调用AI提取标签（支持回退）.

        回退链: Qwen3 → 腾讯混元 → 空列表

        Args:
            text: 备注文本

        Returns:
            AI提取的标签列表
        """
        # 尝试1: Qwen3
        if self.qwen_api_key:
            try:
                result = self._call_qwen_api(text)
                if result:
                    return result
            except (RuntimeError, ImportError, AttributeError, KeyError, IndexError) as e:
                print(f"Qwen3 API调用失败，尝试回退: {str(e)}")

        # 尝试2: 腾讯混元
        if self.hunyuan_secret_id and self.hunyuan_secret_key:
            try:
                result = self._call_hunyuan_api(text)
                if result:
                    return result
            except (RuntimeError, ImportError, AttributeError, KeyError, ValueError) as e:
                print(f"腾讯混元API调用失败: {str(e)}")

        # 所有AI都失败，返回空列表
        return []

    def _call_qwen_api(self, text: str) -> List[TagResult]:
        """
        调用Qwen3 API提取标签（真实实现）.

        使用阿里云DashScope SDK调用通义千问API。
        文档: https://help.aliyun.com/zh/dashscope/developer-reference/api-details

        Args:
            text: 备注文本

        Returns:
            AI提取的标签列表
        """
        if not self.qwen_api_key:
            return []

        try:
            import dashscope
            from http import HTTPStatus

            # 配置API Key
            dashscope.api_key = self.qwen_api_key

            # 调用Qwen3 API (使用qwen-turbo模型)
            # dashscope 的类型定义不够精确，需要忽略类型检查
            response = dashscope.Generation.call(
                model="qwen-turbo",
                messages=[{"role": "user", "content": self._build_qwen_prompt(text)}],
                result_format="message",
                temperature=0.3,
                top_p=0.8,
                max_tokens=500,
            )

            # 检查响应状态 (type: ignore 用于绕过类型检查)
            if hasattr(response, "status_code") and response.status_code == HTTPStatus.OK:
                # 解析AI返回的JSON
                ai_text = response.output.choices[0]["message"]["content"]
                return self._parse_qwen_response(ai_text)
            elif hasattr(response, "code"):
                print(f"Qwen3 API错误: {response.code} - {response.message}")
                return []
            else:
                print("Qwen3 API返回了未预期的响应格式")
                return []

        except ImportError:
            print("警告: dashscope库未安装，请运行: pip install dashscope")
            return []
        except (AttributeError, KeyError, IndexError) as e:
            print(f"Qwen3 API响应解析失败: {str(e)}")
            return []
        except RuntimeError as e:
            print(f"Qwen3 API调用失败: {str(e)}")
            return []

    def _parse_qwen_response(self, ai_text: str) -> List[TagResult]:
        """解析Qwen3返回的JSON字符串."""
        try:
            # 移除可能的markdown代码块标记
            ai_text = re.sub(r"```json\s*|\s*```", "", ai_text).strip()

            # 解析JSON
            data = json.loads(ai_text)

            # 提取tags数组
            tags = data.get("tags", [])
            return [
                TagResult(name=tag["name"], confidence=float(tag.get("confidence", 0.85)))
                for tag in tags
                if "name" in tag
            ]

        except (json.JSONDecodeError, KeyError, ValueError) as e:
            print(f"AI响应解析失败: {str(e)}, 原始文本: {ai_text}")
            return []

    def _build_qwen_prompt(self, text: str) -> str:
        """构建Qwen3 Prompt."""
        return f"""你是房产CRM数据分析专家。从客户备注中提取需求标签。

备注内容：
{text}

提取规则：
1. 识别配套需求：可养宠物、需要车位、近地铁、学区房、商圈、医院
2. 识别环境要求：安静、采光好、朝向南、高楼层、低楼层、景观好
3. 识别特殊需求：短租、拎包入住、可注册公司、可改造、押一付一
4. 每个标签给出置信度评分(0-1)

输出JSON格式（不要markdown代码块）：
{{
  "tags": [
    {{"name": "标签名", "confidence": 0.95}},
    ...
  ],
  "summary": "30字核心需求摘要"
}}
"""

    def _merge_tags(self, rule_tags: List[TagResult], ai_tags: List[TagResult]) -> List[TagResult]:
        """合并规则引擎和AI标签（去重，保留高置信度）."""
        tag_dict = {}

        # 先添加规则标签
        for tag in rule_tags:
            tag_dict[tag.name] = tag

        # AI标签覆盖或补充
        for tag in ai_tags:
            if tag.name not in tag_dict or tag.confidence > tag_dict[tag.name].confidence:
                tag_dict[tag.name] = tag

        # 按置信度排序
        return sorted(tag_dict.values(), key=lambda x: x.confidence, reverse=True)

    def _generate_summary(self, tags: List[TagResult], text: str) -> str:
        """生成摘要（截取前50字）."""
        if not tags:
            return text[:50] if text else ""

        tag_names = [t.name for t in tags[:5]]
        summary = f"需求: {', '.join(tag_names)}"
        return summary[:100]

    def _call_hunyuan_api(self, text: str) -> List[TagResult]:
        """
        调用腾讯混元API提取标签（备选方案）- 使用hunyuan-lite模型.

        文档: https://cloud.tencent.com/document/product/1729

        Args:
            text: 备注文本

        Returns:
            AI提取的标签列表
        """
        if not (self.hunyuan_secret_id and self.hunyuan_secret_key):
            return []

        try:
            import hmac
            import time
            from datetime import datetime
            import requests

            # 腾讯混元API参数
            service = "hunyuan"
            host = "hunyuan.tencentcloudapi.com"
            endpoint = f"https://{host}"
            region = "ap-beijing"
            action = "ChatCompletions"
            version = "2023-09-01"
            algorithm = "TC3-HMAC-SHA256"

            # 构建请求
            timestamp = int(time.time())
            date_str = datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%d")

            payload = {
                "Model": "hunyuan-lite",
                "Messages": [
                    {
                        "Role": "user",
                        "Content": self._build_qwen_prompt(text),  # 复用相同的prompt
                    }
                ],
                "Temperature": 0.3,
                "TopP": 0.8,
            }
            payload_str = json.dumps(payload)

            # 步骤1: 构建CanonicalRequest
            http_request_method = "POST"
            canonical_uri = "/"
            canonical_querystring = ""
            ct = "application/json"
            canonical_headers = f"content-type:{ct}\nhost:{host}\n"
            signed_headers = "content-type;host"
            hashed_request_payload = hashlib.sha256(payload_str.encode("utf-8")).hexdigest()
            canonical_request = (
                f"{http_request_method}\n{canonical_uri}\n{canonical_querystring}\n"
                f"{canonical_headers}\n{signed_headers}\n{hashed_request_payload}"
            )

            # 步骤2: 构建StringToSign
            credential_scope = f"{date_str}/{service}/tc3_request"
            hashed_canonical_request = hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()
            string_to_sign = (
                f"{algorithm}\n{timestamp}\n{credential_scope}\n{hashed_canonical_request}"
            )

            # 步骤3: 计算签名
            def _sign(key: bytes, msg: str) -> bytes:
                return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()

            secret_date = _sign(f"TC3{self.hunyuan_secret_key}".encode("utf-8"), date_str)
            secret_service = _sign(secret_date, service)
            secret_signing = _sign(secret_service, "tc3_request")
            signature = hmac.new(
                secret_signing, string_to_sign.encode("utf-8"), hashlib.sha256
            ).hexdigest()

            # 步骤4: 构建Authorization
            authorization = (
                f"{algorithm} Credential={self.hunyuan_secret_id}/{credential_scope}, "
                f"SignedHeaders={signed_headers}, Signature={signature}"
            )

            # 发送请求
            headers = {
                "Authorization": authorization,
                "Content-Type": ct,
                "Host": host,
                "X-TC-Action": action,
                "X-TC-Timestamp": str(timestamp),
                "X-TC-Version": version,
                "X-TC-Region": region,
            }

            response = requests.post(endpoint, headers=headers, data=payload_str, timeout=10)

            if response.status_code == 200:
                result = response.json()
                # 解析响应
                if "Response" in result:
                    if "Error" in result["Response"]:
                        error = result["Response"]["Error"]
                        print(f"腾讯混元API错误: {error.get('Code')} - {error.get('Message')}")
                        return []
                    elif "Choices" in result["Response"]:
                        content = result["Response"]["Choices"][0]["Message"]["Content"]
                        return self._parse_qwen_response(content)  # 复用解析逻辑
                print(f"腾讯混元API响应格式异常: {result}")
                return []
            else:
                print(f"腾讯混元API调用失败: HTTP {response.status_code}")
                return []

        except ImportError:
            print("警告: requests库未安装")
            return []
        except (RuntimeError, ValueError, KeyError, AttributeError) as e:
            print(f"腾讯混元API调用异常: {str(e)}")
            return []

    def clear_cache(self) -> None:
        """清空LRU缓存（用于测试或重置）."""
        self._extract_cached.cache_clear()  # pyright: ignore[reportCallIssue]

    def cache_info(self) -> dict[str, Any]:
        """获取缓存统计信息."""
        # cache_info() 是 lru_cache 装饰器添加的方法
        info = self._extract_cached.cache_info()  # pyright: ignore[reportCallIssue]
        return {
            "hits": info.hits,
            "misses": info.misses,
            "maxsize": info.maxsize,
            "currsize": info.currsize,
            "hit_rate": (
                info.hits / (info.hits + info.misses) if (info.hits + info.misses) > 0 else 0.0
            ),
        }
